<template>
    <div class="uploader">
        <div class="upload-area" :class="{ 'is-dragover': props.drag && isDragOver }" v-on="events">
            <slot v-if="isUploading" name="loading">
                <button disabled>正在上传</button>
            </slot>
            <slot name="uploaded" v-else-if="lastFileData && lastFileData.loaded" :uploadedData="lastFileData.data">
                <button>点击上传</button>
            </slot>
            <slot v-else name="default">
                <button>点击上传</button>
            </slot>
        </div>
        <input type="file" style="display: none" ref="fileInputRef" @change="handleFileChange" />
        <ul :class="`upload-list upload-list-${props.listType}`" v-if="props.showUploadList">
            <li :class="`uploaded-file upload-${item.status}`" v-for="item in fileList" :key="item.uid">

                <img v-if="props.listType === 'picture' && item.url" :src="item.url" :alt="item.name" srcset="" />
                <span class="filename">{{ item.name }}</span>
                <span class="delete-icon" @click="removeFile(item.uid)" :class="`delete-icon-${item.status}`">
                    <LoadingOutlined class="loading-icon" v-if="item.status === 'loading'" />
                    <DeleteOutlined v-else />
                </span>

            </li>
        </ul>
    </div>
</template>
<script setup lang="ts">
import { computed, onBeforeMount, ref, type PropType } from 'vue'
import { v4 as uuidv4 } from 'uuid'
import axios, { type AxiosRequestHeaders, type AxiosResponse } from 'axios';
import { last } from 'lodash-es'
import { DeleteOutlined, LoadingOutlined } from '@ant-design/icons-vue'

export type CheckUpload = (file: File)=>boolean | Promise<File>
type UploadStatus = 'ready' | 'loading' | 'success' | 'error'
type FileListType = 'picture' | 'text'
export interface UploadFile {
    uid: string
    size: number
    name: string
    status: UploadStatus
    raw: File
    resp?: any
    // 上传为图书时候的预览地址
    url?: string
}
const emits = defineEmits(['on-success', 'on-error', 'on-progress', 'on-change'])
const props = defineProps({
    name: {
        type: String,
        default: 'file'
    },
    // input accept file type
    accept: {
        type: String,
        required: false
    },
    beforeUpload:{
        type: Function as PropType<CheckUpload>
    },
    listType: {
        type: String as PropType<FileListType>,
        default: 'text'
    },
    autoUpload: {
        type: Boolean,
        default: true
    },
    action: {
        type: String,
        required: true
    },
    // 携带 cookie
    withCredentials: {
        type: Boolean,
        default: false
    },
    // request header
    headers: {
        type: Object as PropType<AxiosRequestHeaders>,
        default: () => {
            return { 'Content-Type': 'multipart/form-data' }
        }
    },
    showUploadList: {
        type: Boolean,
        default: true
    },
    drag: {
        type: Boolean,
        default: false
    },
})

const fileList = ref<UploadFile[]>([])
const fileInputRef = ref<HTMLInputElement | null>(null)

const isUploading = computed(() => {
    return fileList.value.some((item) => item.status === 'loading')
})
const isDragOver = ref(false)

const handleDrag = (e: DragEvent, over: boolean)=>{
    e.preventDefault()
    isDragOver.value = over
}
const uploadFiles = () => {
    fileList.value
        .filter((file) => file.status === 'ready')
        .forEach((readyFile) => postFile(readyFile))
}
const handleDrop =(e:DragEvent)=>{
    e.preventDefault()
    isDragOver.value = false
    if(!e.dataTransfer?.files) {
        return
    }
    beforeUploadCheck(e.dataTransfer.files)
}

const lastFileData = computed(()=>{
    const lastFile = last(fileList.value)
    if (!lastFile) {
        return false
    }
    return {
        loaded: lastFile.status === 'success',
        data: lastFile.resp
    }
})
const triggerUpload =()=>{
    if(!fileInputRef.value) return
    fileInputRef.value.click()
}
let events:{[key:string]: (e:any)=>void} = {
    click:triggerUpload,
    dragover: (e: DragEvent)=>{
        handleDrag(e, true)
    },
    dragleave: (e) => { handleDrag(e, false) },
    drop: handleDrop
}

const beforeUploadCheck = async(files: FileList)=>{
    const uploadedFile = files[0]
    if (props.beforeUpload) {
        const result = props.beforeUpload(uploadedFile)
        if (result instanceof Promise) {
            // 判断 processFile 是否为文件类型
            const processFile = await result
            if (processFile instanceof File) {
                await addFileToList(processFile)
            } else {
                throw new Error('beforeUpload must return a boolean or Promise<File>')
            }
        } else if (result === true) {
            await addFileToList(uploadedFile)
        }
    } else {
        await addFileToList(uploadedFile)
    }

}
const addFileToList = async (uploadedFile: File)=>{
    const fileObj = ref<UploadFile>({
        uid: uuidv4(),
        size: uploadedFile.size,
        name: uploadedFile.name,
        status: 'ready',
        raw: uploadedFile
    })

    if(props.listType === 'picture') {
        try {
            fileObj.value.url = URL.createObjectURL(fileObj.value.raw)
        } catch (error) {
            console.log('upload file error', error)
        }
/*         try {
            const fileReader = new FileReader()
            fileReader.readAsDataURL(uploadedFile)
            fileReader.addEventListener('load',()=>{
                fileObj.value.url = fileReader.result as string
            })
        } catch (error) {
            
        } */
    }
    fileList.value.push(fileObj.value)
    if (props.autoUpload) {
        await postFile(fileObj.value)
    }
}
const postFile= async(readyFile: UploadFile)=>{
    const formData = new FormData()
    formData.append(props.name, readyFile.raw)

    let res: AxiosResponse | null = null
    try {
        //上传相同的文件，onchange事件不会被出发，需要上传完成后，重置value
        if(fileInputRef.value) {
            fileInputRef.value.value = ''
        }

        readyFile.status = 'loading'
        res= await axios.post(props.action, formData, {
            headers: props.headers,
            withCredentials: props.withCredentials
        })
        
    } catch (e) {
        readyFile.status = 'error'
        emits('on-error', { error: e, file: readyFile, list: fileList })
    } finally {
        if(fileInputRef.value) {
            fileInputRef.value.value = ''
        }
    }
    if (!res || !res.data) {
        emits('on-error', '服务端返回信息错误')
        readyFile.status = 'error'
        return
    }
    readyFile.status = 'success'
    emits('on-success', { resp: res.data, file: readyFile, list: fileList })
    readyFile.resp = res.data
}
const handleFileChange = async(e:Event)=>{
    const target = e.target as HTMLInputElement
    if (!target.files) return
    emits('on-change', e, fileList)
    await beforeUploadCheck(target.files)
}
const removeFile = async(uid: UploadFile['uid'])=>{
    const index = fileList.value.findIndex((item) => item.uid === uid)

    if (index == -1) return

    const item = fileList.value[index]

    if (item.status === 'loading') {
        item.status = 'ready'
    }
    fileList.value.splice(index, 1)

}
</script>
<style scoped lang="scss">
.uploader {
    margin-left: 8px;

    .upload-list {
        list-style-type: none;
        padding: 0;
        margin: 0;
        margin-left: 2px;

        .uploaded-file {
            display: flex;
            align-items: center;
            line-height: 28px;
            margin-bottom: 8px;
            img {width: 50px;}

            &:hover {
                .delete-icon {
                    display: flex;
                }
            }

            &.upload-loading {
                color: #ccc;

                &::before {
                    background: #ccc;
                }
            }

            &.upload-success {
                color: green;

                &::before {
                    background: green;
                }
            }

            &.upload-error {
                color: red;

                &::before {
                    background: red;
                }
            }

            &::before {
                content: '';
                display: block;
                background: black;
                height: 20px;
                width: 3px;
                margin-right: 8px;
            }

            .filename {
                font-size: 22px;
            }

            .delete-icon {
                font-size: 18px;
                padding-left: 8px;
                cursor: pointer;
                color: initial;
                height: 28px;
                display: none;
                align-items: center;

                &.delete-icon-loading {
                    display: flex;
                    cursor: auto;
                }
            }
        }
    }
}
</style>